package com.lemon.exam.common.resolver;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lemon.exam.common.annotation.NestedParam;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.function.Function;

/**
 * 嵌套参数解析器
 *
 * @author Lemon
 * @since 2025/3/7 13:35
 */
public class NestedParamResolver implements HandlerMethodArgumentResolver {
    private final ObjectMapper objectMapper;

    public NestedParamResolver(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(NestedParam.class);
    }

    @Override
    @NonNull
    public Mono<Object> resolveArgument(@NonNull MethodParameter parameter, @NonNull BindingContext bindingContext, ServerWebExchange exchange) {
        return Mono.justOrEmpty(exchange.getRequest())
                .filter(Objects::nonNull)
                .map(ServerHttpRequest::getQueryParams)
                .filter(map -> !CollectionUtils.isEmpty(map))
                .map(toObject(parameter));
    }

    /**
     * 将嵌套参数转换为对象
     *
     * @param parameter
     * @return
     */
    private Function<MultiValueMap<String, String>, Object> toObject(MethodParameter parameter) {
        return map -> {
            Map<String, Object> result = new HashMap<>();
            for (Map.Entry<String, List<String>> entry : map.entrySet()) {
                String key = entry.getKey();
                List<String> value = entry.getValue().stream().filter(StringUtils::hasText).toList();
                if (CollectionUtils.isEmpty(value) || !StringUtils.hasText(key)) {
                    continue;
                }
                toMap(split(key), result, value);
            }
            if (Map.class.equals(parameter.getParameterType())) {
                return result;
            } else {
                return objectMapper.convertValue(result, parameter.getParameterType());
            }
        };
    }

    /**
     * 将字符串分割成数组
     *
     * @param s
     * @return
     */
    private static String[] split(String s) {
        String[] str = s.split("[\\[\\]]");
        String[] ret = new String[str.length];
        int index = 0;
        for (String value : str) {
            if (StringUtils.hasText(value.trim())) {
                ret[index++] = value;
            }
        }
        return Arrays.copyOfRange(ret, 0, index);
    }

    /**
     * 将嵌套参数转换为对象
     *
     * @param keys
     * @param result
     * @param value
     */
    @SuppressWarnings("unchecked")
    private static void toMap(String[] keys, Map<String, Object> result, List<String> value) {
        if (keys.length == 1) {
            result.put(keys[0], value.size() == 1 ? value.get(0) : value);
        } else {
            Stack<Map<String, Object>> stack = new Stack<>();
            Map<String, Object> preChild = new HashMap<>();
            if (result.containsKey(keys[0])) {
                preChild = (Map<String, Object>) result.get(keys[0]);
            }
            stack.push(preChild);
            for (int i = 1; i < keys.length - 1; i++) {
                Map<String, Object> nextChild = new HashMap<>();
                if (preChild.containsKey(keys[i])) {
                    nextChild = (Map<String, Object>) preChild.get(keys[i]);
                }
                stack.push(nextChild);
                preChild = nextChild;
            }
            Map<String, Object> pop = stack.pop();
            pop.put(keys[keys.length - 1], value.size() == 1 ? value.get(0) : value);
            for (int i = keys.length - 2; i > 0; i--) {
                Map<String, Object> next = stack.pop();
                next.put(keys[i], pop);
                pop = next;
            }
            result.put(keys[0], pop);
        }
    }
}
